//============================================================================
// BytePacket.cs
//
// Copyright (c) 2005 U.S. TrailMaps, LLC
// THIS MATERIAL IS CONFIDENTIAL AND PROPRIETARY TO U.S. TRAILMAPS AND
// MAY NOT BE REPRODUCED, PUBLISHED OR DISCLOSED TO OTHERS WITHOUT COMPANY
// AUTHORIZATION.
//
// This work is derived from Waymex GPS Library for .Net version 2.7.9 source.
// Portions copyright (c) 2004-2005 Waymex IT Ltd
//============================================================================

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Waymex.Gps.Garmin.Usb
{
	internal class BytePacket
	{
		private const int _defaultCapacity = 16;

		private byte[] _array;
		private int _size;

		public BytePacket() : this(_defaultCapacity)
		{
		}

		public BytePacket(int capacity)
		{
			if (capacity < 0)
				throw new ArgumentOutOfRangeException("capacity");
			_array = new byte[capacity];
		}

		public BytePacket(byte[] data) : this(data.Length)
		{
			Array.Copy(data, 0, _array, 0, data.Length);
			_size = data.Length;
		}

		public BytePacket(IntPtr values, int count) : this(count)
		{
			int valuesIndex = 0;
			while (count-- > 0)
				_array[_size++] = Marshal.ReadByte(values, valuesIndex++);
		}

		public int Capacity
		{
			get
			{
				return _array.Length;
			}
			set
			{
				if (value != _array.Length)
				{
					if (value < _size)
						throw new ArgumentOutOfRangeException("value");
					if (value > 0)
					{
						byte[] newArray = new byte[value];
						if (_size > 0)
							Array.Copy(_array, 0, newArray, 0, _size);
						_array = newArray;
					}
					else
						_array = new byte[_defaultCapacity];
				}
			}
		}

		public int Length
		{
			get
			{
				return _size;
			}
		}

		public void Clear()
		{
			SetLengthAndClear(0);
		}

		public void SetLengthAndClear(int length)
		{
			EnsureCapacity(length);
			_size = length;
			if (_size > 0)
				Array.Clear(_array, 0, _size);
		}

		private void EnsureCapacity(int min)
		{
			if (_array.Length < min)
			{
				int newCapacity = (_array.Length == 0) ? _defaultCapacity : (_array.Length * 2);
				if (newCapacity < min)
					newCapacity = min;
				Capacity = newCapacity;
			}
		}

		public byte this[int index]
		{
			get
			{
				if (index < 0 || index >= _size)
					throw new ArgumentOutOfRangeException("index");
				return _array[index];
			}
			set
			{
				if (index < 0 || index >= _size)
					throw new ArgumentOutOfRangeException("index");
				_array[index] = value;
			}
		}

		public byte GetByte(int index)
		{
			return this[index];
		}

		public byte GetNextByte(ref int index)
		{
			byte value = this[index];
			++index;
			return value;
		}

		public void SetByte(int index, byte value)
		{
			this[index] = value;
		}

		public void AppendByte(byte value)
		{
			EnsureCapacity(_size + 1);
			_array[_size] = value;
			_size++;
		}

		public void AppendRepeatedByte(byte value, int repeats)
		{
			while (repeats > 0)
			{
				AppendByte(value);
				--repeats;
			}
		}

		public byte[] GetBytes(int index, int count)
		{
			if (index < 0 || index > _size)
				throw new ArgumentOutOfRangeException("index");
			if (count < 0 || index + count > _size)
				throw new ArgumentOutOfRangeException("count");
			byte[] bytes = new byte[count];
			Array.Copy(_array, index, bytes, 0, count);
			return bytes;
		}

		public byte[] GetNextBytes(ref int index, int count)
		{
			byte[] value = GetBytes(index, count);
			index += count;
			return value;
		}

		public void SetBytes(int index, byte[] values)
		{
			SetBytes(index, values, 0, values.Length);
		}

		public void SetBytes(int index, byte[] values, int valuesIndex, int count)
		{
			if (index < 0 || index > _size)
				throw new ArgumentOutOfRangeException("index");
			if (count < 0 || index + count > _size)
				throw new ArgumentOutOfRangeException("count");
			Array.Copy(values, valuesIndex, _array, index, count);
		}

		public void SetBytes(int index, BytePacket packet)
		{
			SetBytes(index, packet._array, 0, packet.Length);
		}

		public void AppendBytes(byte[] values)
		{
			AppendBytes(values, 0, values.Length);
		}

		public void AppendBytes(byte[] values, int valuesIndex, int count)
		{
			EnsureCapacity(_size + count);
			Array.Copy(values, valuesIndex, _array, _size, count);
			_size += count;
		}

		public void AppendBytes(IntPtr values, int valuesIndex, int count)
		{
			EnsureCapacity(_size + count);
			while (count-- > 0)
				_array[_size++] = Marshal.ReadByte(values, valuesIndex++);
		}

		public void AppendBytes(BytePacket packet)
		{
			AppendBytes(packet._array, 0, packet.Length);
		}

		public short GetInt16(int index)
		{
			return BitConverter.ToInt16(_array, index);
		}

		public short GetNextInt16(ref int index)
		{
			short value = GetInt16(index);
			index += 2;
			return value;
		}

		public void SetInt16(int index, short value)
		{
			SetBytes(index, BitConverter.GetBytes(value));
		}

		public void AppendInt16(short value)
		{
			AppendBytes(BitConverter.GetBytes(value));
		}

		public ushort GetUInt16(int index)
		{
			return BitConverter.ToUInt16(_array, index);
		}

		public ushort GetNextUInt16(ref int index)
		{
			ushort value = GetUInt16(index);
			index += 2;
			return value;
		}

		public void SetUInt16(int index, ushort value)
		{
			SetBytes(index, BitConverter.GetBytes(value));
		}

		public void AppendUInt16(ushort value)
		{
			AppendBytes(BitConverter.GetBytes(value));
		}

		public int GetInt32(int index)
		{
			return BitConverter.ToInt32(_array, index);
		}

		public int GetNextInt32(ref int index)
		{
			int value = GetInt32(index);
			index += 4;
			return value;
		}

		public void SetInt32(int index, int value)
		{
			SetBytes(index, BitConverter.GetBytes(value));
		}

		public void AppendInt32(int value)
		{
			AppendBytes(BitConverter.GetBytes(value));
		}

		public uint GetUInt32(int index)
		{
			return BitConverter.ToUInt32(_array, index);
		}

		public uint GetNextUInt32(ref int index)
		{
			uint value = GetUInt32(index);
			index += 4;
			return value;
		}

		public void SetUInt32(int index, uint value)
		{
			SetBytes(index, BitConverter.GetBytes(value));
		}

		public void AppendUInt32(uint value)
		{
			AppendBytes(BitConverter.GetBytes(value));
		}

		public float GetSingle(int index)
		{
			return BitConverter.ToSingle(_array, index);
		}

		public float GetNextSingle(ref int index)
		{
			float value = GetSingle(index);
			index += 4;
			return value;
		}

		public void SetSingle(int index, float value)
		{
			SetBytes(index, BitConverter.GetBytes(value));
		}

		public void AppendSingle(float value)
		{
			AppendBytes(BitConverter.GetBytes(value));
		}

		public char GetASCII(int index)
		{
			return Encoding.ASCII.GetChars(_array, index, 1)[0];
		}

		public char GetNextASCII(ref int index)
		{
			char value = GetASCII(index);
			++index;
			return value;
		}

		public void SetASCII(int index, char value)
		{
			SetBytes(index, Encoding.ASCII.GetBytes(new char[] { value }));
		}

		public void AppendASCII(char value)
		{
			AppendBytes(Encoding.ASCII.GetBytes(new char[] { value }));
		}

		public string GetASCIIFixedLength(int index, int length)
		{
			return Encoding.ASCII.GetString(_array, index, length).TrimEnd(' ');
		}

		public string GetNextASCIIFixedLength(ref int index, int length)
		{
			string value = GetASCIIFixedLength(index, length);
			index += length;
			return value;
		}

		public void SetASCIIFixedLength(int index, string value, int length)
		{
			SetBytes(index, Encoding.ASCII.GetBytes(MakeFixedLength(value, length)));
		}

		public void AppendASCIIFixedLength(string value, int length)
		{
			AppendBytes(Encoding.ASCII.GetBytes(MakeFixedLength(value, length)));
		}

		private static string MakeFixedLength(string value, int length)
		{
			if (value.Length < length)
				return value.PadRight(length);
			else
				return value.Substring(0, length);
		}

		public string GetASCIINullTerminated(int index)
		{
			int length = 0;
			while (index + length < _size && _array[index + length] != 0)
				++length;
			return Encoding.ASCII.GetString(_array, index, length);
		}

		public string GetNextASCIINullTerminated(ref int index)
		{
			string value = GetASCIINullTerminated(index);
			index += value.Length + 1;
			if (index > _size)
				index = _size;
			return value;
		}

		public void SetASCIINullTerminated(int index, string value)
		{
			byte[] bytes = Encoding.ASCII.GetBytes(value);
			SetBytes(index, bytes);
			SetByte(index + bytes.Length, 0);
		}

		public void AppendASCIINullTerminated(string value)
		{
			AppendBytes(Encoding.ASCII.GetBytes(value));
			AppendByte(0);
		}

		public byte[] ToArray()
		{
			byte[] newArray = new byte[_size];
			Array.Copy(_array, 0, newArray, 0, _size);
			return newArray;
		}

		public override string ToString()
		{
			if (_size > 0)
			{
				StringBuilder text = new StringBuilder(BitConverter.ToString(_array, 0, _size));
				text.Append(" (");
				for (int index = 0; index < _size; ++index)
				{
					if (_array[index] >= 32 && _array[index] <= 127)
						text.Append(Convert.ToChar(_array[index]));
					else
						text.Append('.');
				}
				text.Append(")");
				return text.ToString();
			}
			else
				return "(empty)";
		}
	}
}
